package simulation;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import view.Canvas;

/**
 * XXX.
 * 
 * @author Robert C. Duvall
 */
public class Model {
	// bounds and input for game
	private final Canvas myView;
	// simulation state
	private List<Mass> myMasses;
	private List<Spring> mySprings;
	private List<Force> myForces;
	private List<Paintable> myPaintables;
	private List<Updatable> myUpdatables;
	private Map<Integer, Force> myForceToggleList;
	private Spring myTempSpring = null;
	private Mass myTempMass = null;
	private int wallSizeOffSet = 0;

	private static final int LOAD = KeyEvent.VK_N;
	private static final int CLEAR = KeyEvent.VK_C;
	private static final int UP = KeyEvent.VK_UP;
	private static final int DOWN = KeyEvent.VK_DOWN;

	private static final int GRAVITYTOGGLE = KeyEvent.VK_G;
	private static final int VISCOSITYTOGGLE = KeyEvent.VK_V;
	private static final int CENTEROFMASSTOGGLE = KeyEvent.VK_M;
	private static final int UPWALLTOGGLE = KeyEvent.VK_1;
	private static final int RIGHTWALLTOGGLE = KeyEvent.VK_2;
	private static final int DOWNWALLTOGGLE = KeyEvent.VK_3;
	private static final int LEFTWALLTOGGLE = KeyEvent.VK_4;
	private static final int TOP_WALL_ID = 1;
	private static final int RIGHT_WALL_ID = 2;
	private static final int BOTTOM_WALL_ID = 3;
	private static final int LEFT_WALL_ID = 4;
	

	private final double DEFAULT_MASS=5;
	private final double DEFAULT_SPRING_CONSTANT=0.5;

	/**
	 * Create a game of the given size with the given display for its shapes.
	 */
	public Model(final Canvas canvas) {
		myView = canvas;
		myMasses = new ArrayList<Mass>();
		mySprings = new ArrayList<Spring>();
		myForces = new ArrayList<Force>();
		myPaintables = new ArrayList<Paintable>();
		myUpdatables = new ArrayList<Updatable>();
		myForceToggleList = new HashMap<Integer, Force>();
		myForceToggleList.put(GRAVITYTOGGLE, new Gravity());
		myForceToggleList.put(VISCOSITYTOGGLE, new Viscosity());
		myForceToggleList.put(CENTEROFMASSTOGGLE, new ConstantCenterOfMass());
		myForceToggleList.put(UPWALLTOGGLE, new ConstantForceWallRepulsion(TOP_WALL_ID));
		myForceToggleList.put(RIGHTWALLTOGGLE, new ConstantForceWallRepulsion(RIGHT_WALL_ID));
		myForceToggleList.put(DOWNWALLTOGGLE, new ConstantForceWallRepulsion(BOTTOM_WALL_ID));
		myForceToggleList.put(LEFTWALLTOGGLE, new ConstantForceWallRepulsion(LEFT_WALL_ID));

	}

	/**
	 * Draw all elements of the simulation.
	 */
	public void paint(Graphics2D pen) {

		for (Paintable p : myPaintables) {
			p.paint(pen);
		}

		if (myTempMass != null && myTempSpring != null) {
			myTempMass.paint(pen);
			myTempSpring.paint(pen);
		}
	}

	/**
	 * Update simulation for this moment, given the time since the last moment.
	 */
	public void update( double elapsedTime ) {
	
		Dimension updatedBounds = processInput();
		for (Updatable u : myUpdatables) {
			u.update(elapsedTime, updatedBounds);
		}
		for (Force f : myForces) {
			f.apply(myMasses);
		}

		if (myTempMass != null && myTempSpring != null) {
			myTempMass.update(elapsedTime, updatedBounds);
			myTempSpring.update(elapsedTime, updatedBounds);
		}

	}
	
	

	/**
	 * Add given mass to this simulation.
	 */
	public void add(Mass mass) {
		myPaintables.add(mass);
		myUpdatables.add(mass);
		myMasses.add(mass);
	}

	/**
	 * Add given spring to this simulation.
	 */
	public void add(Spring spring) {
		myPaintables.add(spring);
		myUpdatables.add(spring);
		mySprings.add(spring);
	}
	
	

	public void add(Force force) {
		myForces.add(force);
	}
	
	

	public void clearTemporaryObjects() {
		myTempMass = null;
		myTempSpring = null;

	}
	
	

	public void clear() {
		myMasses.clear();
		mySprings.clear();
		myUpdatables.clear();
		myPaintables.clear();
	}
	
	

	public Dimension processInput() {
		int lastKeyPressed=myView.getLastKeyPressed();
		Dimension bounds = myView.getSize();
		loadMoreAssemblyOrClearAllAssemblies(lastKeyPressed);
		Dimension updatedBounds = changeWallSize(lastKeyPressed, bounds);
		toggleForces(lastKeyPressed);
		dragNearestMass();

		return updatedBounds;
	}
	
	private Dimension changeWallSize(int lastKeyPressed, Dimension bounds) {
		if (lastKeyPressed == DOWN){
			wallSizeOffSet = wallSizeOffSet + 10;
		}
		if (lastKeyPressed == UP){
			wallSizeOffSet = wallSizeOffSet - 10;
		}
		return new Dimension(bounds.width, bounds.height - wallSizeOffSet);
	}
	
	

	private void loadMoreAssemblyOrClearAllAssemblies(int lastKeyPressed) {
		if (lastKeyPressed == LOAD ) {
			myView.loadModel();
			myView.clearLastKeyPressed();
		}
		if (lastKeyPressed == CLEAR) {
			clear();
		}
	}

	
	
	private void dragNearestMass() {
		Point mousePos = myView.getLastMousePosition();
		if (mousePos != null && myTempMass == null) {
			createTempObjects(mousePos);
		} 
		else if (mousePos == null) {
			clearTemporaryObjects();
		}
	}

	
	
	private void toggleForces(int lastKeyPressed) {
		boolean exist = false;
		if (myForceToggleList.containsKey(lastKeyPressed)){
			Force current = myForceToggleList.get(lastKeyPressed);
			for(Force f : myForces){
				if (f.getName().equalsIgnoreCase(current.getName())){
					f.toggle();
					exist = true;
				}
			}
			if (!exist){
				myForces.add(current);
			}
		}
	}

	private void createTempObjects(Point mousePos) {
		myTempMass = new tempMass(mousePos.x, mousePos.y, DEFAULT_MASS, myView);
		Mass closestMass = closestMassTo(myTempMass);
		myTempSpring = new Spring(myTempMass, closestMass,
				closestMass.distance(myTempMass), DEFAULT_SPRING_CONSTANT);
	}

	public Mass closestMassTo(Mass target) {
		double cur = myMasses.get(0).distance(target);
		Mass ret = myMasses.get(0);
		for (Mass m : myMasses) {
			if (m.distance(target) < cur) {
				cur = m.distance(target);
				ret = m;
			}
		}
		return ret;
	}
}
